/* Fraction.java
 * Implementation of a fraction-based real number class.
 * 
 * © 2007 David Given.
 *
 * $Id: Fraction.java 5 2007-06-25 15:43:39Z david.given $
 */

package com.cowlark.cowcalc;

public class Fraction
{
	static final int NORMAL = 0;
	static final int NAN = 1;
	static final int OVERFLOW = 2;
	
	int type;
	long top;
	long bottom;
	
	Fraction()
	{
		type = NORMAL;
		top = 0;
		bottom = 1;
	}
	
	Fraction(long n)
	{
		type = NORMAL;
		top = n;
		bottom = 1;
	}
	
	Fraction(long top, long bottom)
	{
		type = NORMAL;
		this.top = top;
		this.bottom = bottom;
	}
	
	Fraction(Fraction f)
	{
		type = f.type;
		top = f.top;
		bottom = f.bottom;
	}
	
	void set(long top, long bottom)
	{
		type = NORMAL;
		this.top = top;
		this.bottom = bottom;
	}
	
	void set(Fraction f)
	{
		type = f.type;
		top = f.top;
		bottom = f.bottom;
	}
	
	void add(long n)
	{
		add(new Fraction(n, 1));
	}
	
	void add(Fraction f)
	{
		long xt, xb;
		long yt, yb;
		
		if (type != NORMAL)
			return;
		if (f.type != NORMAL)
		{
			type = f.type;
			return;
		}
		
		int factor = 0;
		for (;;)
		{
			xt = top;
			xb = bottom;
			if ((xt > Integer.MAX_VALUE) && (xb > Integer.MAX_VALUE))
			{
				xt >>= factor;
				xb >>= factor;
				if (((xt == 0) && (top != 0)) ||
				    (xb == 0) && (bottom != 0))
				{
					type = OVERFLOW;
					return;
				}
			}
			yt = f.top;
			yb = f.bottom;
			if ((yt > Integer.MAX_VALUE) && (yb > Integer.MAX_VALUE))
			{
				yt >>= factor;
				yb >>= factor;
				if (((yt == 0) && (f.top != 0)) ||
					    (yb == 0) && (f.bottom != 0))
					{
						type = OVERFLOW;
						return;
					}
			}

			try
			{
				// xt = xt*yb + yt*xb;
				// xb = xb*yb;
				xt = Util.add(Util.mul(xt, yb), Util.mul(yt, xb));
				xb = Util.mul(xb, yb);
				break;
			}
			catch (OverflowException e)
			{
				if (factor > 64)
				{
					type = OVERFLOW;
					return;
				}
				
				factor++;
				continue;
			}
		}

		top = xt;
		bottom = xb;
		simplify();
	}
	
	void sub(long n)
	{
		add(-n);
	}
	
	void sub(Fraction f)
	{
		add(new Fraction(-f.top, f.bottom));
	}
	
	void mul(long n)
	{
		mul(new Fraction(n, 1));
	}
	
	void mul(Fraction f)
	{
		long xt, xb;
		long yt, yb;
		
		if (type != NORMAL)
			return;
		if (f.type != NORMAL)
		{
			type = f.type;
			return;
		}
		
		int factor = 0;
		for (;;)
		{
			xt = top;
			xb = bottom;
			if ((xt > Integer.MAX_VALUE) && (xb > Integer.MAX_VALUE))
			{
				xt >>= factor;
				xb >>= factor;
				if (((xt == 0) && (top != 0)) ||
					    (xb == 0) && (bottom != 0))
					{
						type = OVERFLOW;
						return;
					}
			}
			yt = f.top;
			yb = f.bottom;
			if ((yt > Integer.MAX_VALUE) && (yb > Integer.MAX_VALUE))
			{
				yt >>= factor;
				yb >>= factor;
				if (((yt == 0) && (f.top != 0)) ||
					    (yb == 0) && (f.bottom != 0))
					{
						type = OVERFLOW;
						return;
					}
			}
			
			try
			{
				// xt = xt * yt;
				// xb = xb * yb;
				xt = Util.mul(xt, yt);
				xb = Util.mul(xb, yb);
				break;
			}
			catch (OverflowException e)
			{
				if (factor > 64)
				{
					type = OVERFLOW;
					return;
				}
				
				factor++;
				continue;
			}
		}
		
		top = xt;
		bottom = xb;
		simplify();
	}
	
	void div(long n)
	{
		mul(new Fraction(1, n));
	}
	
	void div(Fraction f)
	{
		mul(new Fraction(f.bottom, f.top));
	}
	
	void invert()
	{
		long i = top;
		top = bottom;
		bottom = i;
		simplify();
	}
	
	void sqrt()
	{
		if (type != NORMAL)
			return;
		if (top < 0)
		{
			type = NAN;
			return;
		}
		
		Fraction r = new Fraction(1);
		Fraction xr = new Fraction();
		
		for (int i=0; i<Settings.SqrtIterations; i++)
		{
			xr.set(this);
			xr.div(r);
			xr.add(r);
			xr.div(2);
			r.set(xr);
			r.simplify();
		}
		
		set(r);
	}
	
	public String toString()
	{
		simplify();
		
		switch (type)
		{
			case NORMAL:
			{
				StringBuffer sb = new StringBuffer();
				
				if (Settings.FractionMode)
				{
					sb.append(top);
					
					if (bottom != 1)
					{
						sb.append('/');
						sb.append(bottom);
					}
				}
				else
				{
					if (bottom == 1)
						sb.append(top);
					else
					{
						long t = top;
						long b = bottom;
						
						if (t < 0)
						{
							sb.append('-');
							t = -t;
						}
						
						long l = t/b;
						sb.append(l);
						sb.append('.');
						
						t -= l*b;
						
						int precision = Settings.Precision;
						while ((precision > 0) && (t != 0))
						{
							try
							{
								t = Util.mul(t, 10);
							}
							catch (OverflowException e)
							{
								b /= 10;
								if (b == 0)
									break;
							}
							
							l = t/b;
							sb.append(l);
							t -= l*b;
							
							precision--;
						}
					}
				}
				
				return sb.toString();
			}
			
			case NAN:
				return "NaN";
				
			case OVERFLOW:
				return "Overflow";
		}
		
		return null;
	}

	void simplify()
	{
		if (type != NORMAL)
			return;
		if (bottom == 0)
			return;
		if (top == 0)
		{
			bottom = 1;
			return;
		}
		
		for (;;)
		{
			long g = Util.gcd(Math.abs(top), Math.abs(bottom));
			if (g == 1)
				return;
			
			top /= g;
			bottom /= g;
		}
	}
}
